package com.example.user.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.common.Interface.user.entity.dto.*;
import com.example.common.Interface.user.entity.vo.UserInfoVO;
import com.example.common.Interface.user.service.UserService;
import com.example.common.bloom.BloomFilterHelper;
import com.example.common.bloom.RedisBloomFilter;
import com.example.common.response.BizException;
import com.example.common.util.encryption.Encryption;
import com.example.common.util.lock.RedisLockUtils;
import com.example.user.dao.mapper.UserMapper;
import com.example.user.dao.po.User;
import com.google.common.hash.Funnels;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;

@DubboService
@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private RedisBloomFilter redisBloomFilter;

    private static final Pattern EMAIL_PATTERN = Pattern.compile
            ("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");

    private static final BloomFilterHelper<Long> useridBloomFilterHelper =
            new BloomFilterHelper<>(Funnels.longFunnel(), 100000, 0.01);
    private static final String USERID_BLOOM_FILTER_KEY_PREFIX = "bloomFilter:userId:";
    private static final BloomFilterHelper<CharSequence> emailBloomFilterHelper =
            new BloomFilterHelper<>(Funnels.stringFunnel(StandardCharsets.UTF_8), 100000, 0.01);
    private static final String EMAIL_BLOOM_FILTER_KEY_PREFIX = "emailBloomFilter:email:";
    private static final String userId_KEY_PREFIX = "userId:";
    private static final String LOCK_KEY_PREFIX = "lock:";

    @Resource
    private RedisLockUtils redisLock;

    private Integer random() {
        return (int) (Math.random() * 100);
    }

    @PostConstruct
    public void initBloom() {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(User::getId, User::getEmail);
        List<User> userList = userMapper.selectList(queryWrapper);
        for (User user : userList) {
            redisBloomFilter.addByBloomFilter(useridBloomFilterHelper, USERID_BLOOM_FILTER_KEY_PREFIX, user.getId());
            redisBloomFilter.addByBloomFilter(emailBloomFilterHelper, EMAIL_BLOOM_FILTER_KEY_PREFIX, user.getEmail());
            redisTemplate.opsForValue().set(userId_KEY_PREFIX + "email:" + user.getEmail(), user.getId().longValue());
        }
    }
    @Override
    public RegisterRespDTO register(RegisterReqDTO request){
        log.info("register rpc 服务被调用");

        if(! isValidEmail(request.getEmail())){
            log.info("邮箱格式错误");
            throw new BizException(100, "邮箱格式错误");
        }
        var id = (Number) redisTemplate.opsForValue().get(userId_KEY_PREFIX + "email:" + request.getEmail());
        if(id != null){
            log.info("用户已存在");
            throw new BizException(100, "用户已存在");
        }

        User user = getUserByEmail(request.getEmail());
        if (user != null) {
            log.info("用户已存在");
            throw new BizException(100, "用户已存在");
        }

        String key = LOCK_KEY_PREFIX + userId_KEY_PREFIX + "email:" + request.getEmail();
        Integer value = random();
        Boolean getLock = this.redisLock.getLock(key, value);
        if(getLock){
            log.info("{}:成功获取[{}]锁",Thread.currentThread().getName(), key);
            String email = request.getEmail();
            String password;
            try {
                password = Encryption.md5(request.getPassword());
            }catch (NoSuchAlgorithmException e) {
                log.error("MD5加密失败", e);
                throw new BizException(100, "MD5加密失败");
            }finally {
                redisLock.releaseLock(key,value);
                log.info("{}:释放[{}]锁",Thread.currentThread().getName(),key);
            }
            Date now = new Date();
            User newUser = User.builder()
                    .email(email)
                    .password(password)
                    .createdTime(now)
                    .updatedTime(now)
                    .deleted(false)
                    .build();

            if(insertUser(newUser)){
                redisTemplate.opsForValue().set(userId_KEY_PREFIX + "email:" + newUser.getEmail(), newUser.getId().longValue());
                redisLock.releaseLock(key,value);
                log.info("{}:释放[{}]锁",Thread.currentThread().getName(),key);
                return new RegisterRespDTO(newUser.getId());
            }
            redisLock.releaseLock(key,value);
            log.info("{}:释放[{}]锁",Thread.currentThread().getName(),key);
            throw new BizException(100, "用户正在创建");
        }
        throw new BizException(100, "注册失败");
    }
    @Override
    public LoginRespDTO login(LoginReqDTO request) {
        log.info("login rpc 服务被调用");

        String email = request.getEmail();
        if(! isValidEmail(email)){
            log.info("邮箱格式错误");
            throw new BizException(100, "邮箱格式错误");
        }

        boolean hasUsername = redisBloomFilter.includeByBloomFilter
                (emailBloomFilterHelper, EMAIL_BLOOM_FILTER_KEY_PREFIX, email);
        if (!hasUsername) {
            log.info("Bloom过滤器不存在用户");
            throw new BizException(100, "用户不存在");
        }

        Number userId = (Number) redisTemplate.opsForValue().get(userId_KEY_PREFIX + "email:" + email);
        if(userId == null){
            log.info("数据库中不存在用户");
            throw new BizException(100, "用户不存在");
        }
        if(userId.longValue() == 0){
            throw new BizException(100, "用户不存在");
        }

        User user = null;
        String key = LOCK_KEY_PREFIX + userId_KEY_PREFIX + "email:" + request.getEmail();
        Integer value = random();
        Boolean getLock = this.redisLock.getLock(key, value);
        if(getLock){
            log.info("{}:成功获取[{}]锁",Thread.currentThread().getName(), key);
            if(userId == null){
                user = getUserByEmail(email);
            }else{
                user = getUserById(userId.longValue());
            }
            if(user == null){
                redisLock.releaseLock(key,value);
                log.info("{}:释放[{}]锁",Thread.currentThread().getName(),key);
                throw new BizException(100, "用户不存在");
            }
            redisTemplate.opsForValue().set(userId_KEY_PREFIX + "email:" + email, user.getId());
            redisLock.releaseLock(key,value);
            log.info("{}:释放[{}]锁",Thread.currentThread().getName(),key);
        }

        boolean check;
        try {
            check = Encryption.md5(request.getPassword()).equals(user.getPassword());
        }catch (NoSuchAlgorithmException e) {
            log.error("login rpc MD5加密失败", e);
            throw new BizException(100, "login rpc MD5加密失败");
        }

        if (!check) {
            log.info("login rpc 用户密码错误");
            throw new BizException(100, "用户密码错误");
        }
        return LoginRespDTO.builder()
                .userId(user.getId())
                .build();
    }
    @Override
    public Boolean deleteUser(Long userId) {
        log.info("deleteUser rpc 服务被调用");
        int num = userMapper.deleteById(userId);
        if (num <= 0) {
            log.info("删除失败");
            throw new BizException(100, "删除失败");
        }else if(num > 1){
            log.info("删除异常");
            throw new BizException(100, "删除异常");
        }
        return true;
    }
    @Override
    public Boolean updateUser(Long userId, UpdateReqDTO request) {
        log.info("updateUser rpc 服务被调用");

        boolean hasUsername = redisBloomFilter.includeByBloomFilter
                (useridBloomFilterHelper, USERID_BLOOM_FILTER_KEY_PREFIX, userId);
        if (!hasUsername) {
            log.info("updateUser rpc Bloom过滤器不存在用户");
            throw new BizException(100, "用户不存在");
        }

        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getId, userId);
        User existingUser = userMapper.selectOne(queryWrapper);
        if (existingUser == null) {
            log.info("updateUser rpc 数据库中不存在用户");
            throw new BizException(100, "用户不存在");
        }

        existingUser.setEmail(request.getEmail());
        String password;
        try {
            password = Encryption.md5(request.getPassword());
        }catch (NoSuchAlgorithmException e) {
            log.error("register rpc MD5加密失败", e);
            throw new BizException(100, "register rpc MD5加密失败");
        }
        existingUser.setPassword(password);
        existingUser.setUpdatedTime(new Date());

        int updatedRows = userMapper.updateById(existingUser);

        if (updatedRows > 0) {
            return true;
        } else {
            throw new BizException(100, "更新失败");
        }
    }

    @Override
    public UserInfoVO getUserInfo(Long userId) {
        log.info("getUserInfo rpc 服务被调用");
        //todo 整合所有信息
        return null;
    }

    @Override
    public Boolean logout(Long userId) {
        log.info("logout rpc 服务被调用");
        return true;
    }

    public User getUserById(Long userId) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getId, userId);
        User user = userMapper.selectOne(queryWrapper);

        return user;
    }

    public User getUserByEmail(String email) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getEmail, email);
        User user = userMapper.selectOne(queryWrapper);

        return user;
    }

    public boolean insertUser(User user) {
        int num = userMapper.insert(user);
        if (num <= 0) {
            log.info("插入失败");
            throw new BizException(100, "插入失败");
        }else if(num > 1){
            log.info("插入异常");
            throw new BizException(100, "插入异常");
        }

        redisBloomFilter.addByBloomFilter
                (useridBloomFilterHelper, USERID_BLOOM_FILTER_KEY_PREFIX, user.getId());
        redisBloomFilter.addByBloomFilter
                (emailBloomFilterHelper, EMAIL_BLOOM_FILTER_KEY_PREFIX, user.getEmail());

        return true;
    }

    public static boolean isValidateUser(User user) {
        if (user.getId() == null || user.getId() <= 0) {
            return false;
        }

        String email = user.getEmail();
        if (! isValidEmail(email)) {
            return false;
        }

        String password = user.getPassword();
        if (password == null || password.isEmpty()) {
            return false;
        }

        Date createdTime = user.getCreatedTime(), updatedTime = user.getUpdatedTime(), now = new Date();
        if (createdTime == null || now.before(createdTime)) {
            return false;
        }

        if (updatedTime == null || updatedTime.before(createdTime)) {
            return false;
        }
        return true;
    }

    private static boolean isValidEmail(String email) {
        if (email == null || email.isEmpty() ||!EMAIL_PATTERN.matcher(email).matches()) {
            return false;
        }
        return true;
    }
}
